﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Concurrent;
using System.Runtime.Caching;
using System.Threading;
using Web7.Dispose;
using Web7.Extensions;

namespace Web7.Caching
{
    public class RuntimeCacheProvider : AbstractCacheProvider
    {
        public RuntimeCacheProvider()
        {
            _memoryCache = new MemoryCache("in-memory");
        }

        public RuntimeCacheProvider(ObjectCache memoryCache)
        {
            _memoryCache = memoryCache;
        }

        private static readonly ReaderWriterLockSlim StaticLocker = new ReaderWriterLockSlim();
        private static RuntimeCacheProvider _singleInstance;
        public static RuntimeCacheProvider Default
        {
            get
            {
                using (new WriteLockDisposable(StaticLocker))
                {
                    if (_singleInstance == null) _singleInstance = new RuntimeCacheProvider();
                }
                return _singleInstance;
            }
        }

        private ObjectCache _memoryCache;
        private readonly ConcurrentDictionary<string, Web7CacheKey> _keyTracker = new ConcurrentDictionary<string, Web7CacheKey>();

        public override CacheModificationResult AddOrChange<T>(Web7CacheKey key, CacheValueOf<T> cacheObject)
        {
            using (new WriteLockDisposable(ThreadLocker))
            {
                var exists = _memoryCache.GetCacheItem(key) != null;
                var cacheItemPolicy = cacheObject.Policy.ToCacheItemPolicy();

                var entryDate = DateTime.Now;
                //var policyExpiry = cacheItemPolicy.AbsoluteExpiration.Subtract(entryDate);
                //cacheItemPolicy.RemovedCallback +=arguments =>{ };

                _keyTracker.AddOrUpdate(key, key, (existingKey, existingValue) => key);
                if (exists)
                {
                    // LogHelper.TraceIfEnabled(GetType(), "Updating item with {0} left to run", () => policyExpiry.TotalSeconds.ToString());
                    _memoryCache.Set(key, cacheObject, cacheItemPolicy);
                    return new CacheModificationResult(true, false);
                }
                //var diff = cacheObject.Policy.GetExpiryDate().Subtract(DateTimeOffset.Now);
                // LogHelper.TraceIfEnabled(GetType(), "Adding item with {0} left to run", () => policyExpiry.TotalSeconds.ToString());
                _memoryCache.Add(key, cacheObject, cacheItemPolicy);
            }
            return new CacheModificationResult(false, true);
        }

        public override void Clear()
        {
            _memoryCache.Select(x => x.Key).ToArray().ForEach(x => _memoryCache.Remove(x));
            _memoryCache.DisposeIfDisposable();
            _memoryCache = new MemoryCache("in-memory");
        }

        public override bool Remove(Web7CacheKey key)
        {
            Web7CacheKey throwaway;
            var keyBeSure= _keyTracker.TryGetValue(key, out throwaway);
            object itemRemoved = _memoryCache.Remove(key);
            _keyTracker.TryRemove(key, out throwaway);

            return itemRemoved != null;
        }

        protected override CacheEntry<T> PerformGet<T>(Web7CacheKey key)
        {
            var item = _memoryCache.Get(key);
            var casted = item as ICacheValueOf<T>;
            return casted != null ? new CacheEntry<T>(key, casted) : null;
        }

        public override IEnumerable<Web7CacheKey<T>> GetKeysMatching<T>(Func<T, bool> predicate)
        {
            return _keyTracker.Keys.Select(key =>
            {
                var originalString = key;
                var convertKey = (Web7CacheKey<T>)originalString;
                return new { Key = originalString, Converted = convertKey };
            })
            .Where(x => x.Converted != default(Web7CacheKey<T>) && x.Converted.Original != null && predicate.Invoke(x.Converted.Original))
            .Select(key => key.Converted);
        }

        #region Overrides of DisposableObject

        /// <summary>
        /// 处理资源。从抽象类派生过来的 <see cref="DisposableObject"/> 处理所需的锁定逻辑
        /// </summary>
        protected override void DisposeResources()
        {
            _memoryCache.DisposeIfDisposable();
        }

        #endregion
    }
}
